/*
    socklibpp library
    Copyright (C) 2005  Daniel K. O. <danielosmari at users.sf.net>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/


/**     \file sockbase.h
        \brief Portability layer class and convenience routines.
*/

#ifndef SOCK_BASE_H
#define SOCK_BASE_H


#include "sockaddr.h"

/**     \typedef socket_type
        \brief A typedef for an appropriate integral type to represent a socket.
*/


namespace socklib {


/**     \brief The socklib::base class represents a socket. All common socket
        routines are wrapped as member functions.
*/

class base
{
public:
        /// Address format specifications, for the create() member.
        enum format {
                pf_unspec       = PF_UNSPEC,    ///< Unspecified
                pf_unix         = PF_UNIX,      ///< Unix pipes
                pf_inet         = PF_INET,      ///< IPv4 format
                pf_inet6        = PF_INET6,     ///< IPv6 format
                pf_max          = PF_MAX
        };
        
        /// Type specification for the create() method.
        enum type {
                sock_stream     = SOCK_STREAM,  ///< Stream sockets (TCP)
                sock_dgram      = SOCK_DGRAM,   ///< Datagram sockets
                sock_raw        = SOCK_RAW,     ///< Raw sockets
                sock_rdm        = SOCK_RDM,
                sock_seqpacket  = SOCK_SEQPACKET
        };

        /// Protocol to be used on this socket.
        enum protocol {
                proto_ip        = IPPROTO_IP,   ///< Default protocol for the
                                                /// specified socket type.
                proto_icmp      = IPPROTO_ICMP, ///< ICMP
                proto_tcp       = IPPROTO_TCP,  ///< TCP
                proto_pup       = IPPROTO_PUP,
                proto_udp       = IPPROTO_UDP,  ///< UDP
                proto_idp       = IPPROTO_IDP,
                proto_raw       = IPPROTO_RAW,
                proto_max       = IPPROTO_MAX
                /* TODO (DanielKO#2#): Support IPv6 protocol options */
        };

        enum {
                invalid_socket  = INVALID_SOCKET        ///< An invalid socket
        };

        /// Flags to use with recv/send methods
        enum msgflag {
                msg_none        = 0,            ///< Default
                msg_oob         = MSG_OOB,      ///< send/recv out-of-band data
                                                /// (sock_stream only).
                msg_peek        = MSG_PEEK,     ///< Peek at the incoming data.
                msg_dontroute   = MSG_DONTROUTE ///< Should not be subject to routing
        };

        /// Flags for the shutdown() method
        enum shuthow {
                shut_recv       = 0,    ///< Disallows further recv() calls
                shut_send       = 1,    ///< Disallows further send() calls
                shut_both       = 2     ///< Disallows both send() and recv() calls
        };

        /// Errors that may be set by the different socklib::base operations.
        enum errors {
                /* TODO (DanielKO#2#): Add more error enums here */
                socket_error    = SOCKET_ERROR, ///< Error value used as result for some functions

                eintr           = WSAEINTR,
                ebadf           = WSAEBADF,
                eacces          = WSAEACCES,
                efault          = WSAEFAULT,
                einval          = WSAEINVAL,
                emfile          = WSAEMFILE,
                ewouldblock     = WSAEWOULDBLOCK,       ///< The requested operation would block, try again later
                einprogress     = WSAEINPROGRESS,
                ealready        = WSAEALREADY,
                enotsock        = WSAENOTSOCK,
                edestaddrreq    = WSAEDESTADDRREQ,
                emsgsize        = WSAEMSGSIZE,
                eprototype      = WSAEPROTOTYPE,
                enoprotoopt     = WSAENOPROTOOPT,
                eprotonosupport = WSAEPROTONOSUPPORT,
                esocktnotsupport = WSAESOCKTNOSUPPORT,
                eopnotsupp      = WSAEOPNOTSUPP,
                epfnosupport    = WSAEPFNOSUPPORT,
                afnosupport     = WSAEAFNOSUPPORT,
                eaddrinuse      = WSAEADDRINUSE,
                eaddrnotavail   = WSAEADDRNOTAVAIL,
                enetdown        = WSAENETDOWN,
                enetunreach     = WSAENETUNREACH,
                enetreset       = WSAENETRESET,
                econnaborted    = WSAECONNABORTED,
                econnreset      = WSAECONNRESET,
                enobufs         = WSAENOBUFS,
                eisconn         = WSAEISCONN,
                enotconn        = WSAENOTCONN,
                eshutdown       = WSAESHUTDOWN,
                etoomanyrefs    = WSAETOOMANYREFS,
                etimedout       = WSAETIMEDOUT,
                econnrefused    = WSAECONNREFUSED,
                eloop           = WSAELOOP,
                enametoolong    = WSAENAMETOOLONG,
                ehostdown       = WSAEHOSTDOWN,
                ehostunreach    = WSAEHOSTUNREACH,
                host_not_found  = WSAHOST_NOT_FOUND,
                try_again       = WSATRY_AGAIN,
                no_recovery     = WSANO_RECOVERY,
                no_data         = WSANO_DATA
        };

        /// Flags to use in the base::ioctl method
        enum ioctl_cmd {
                fionbio         = FIONBIO,      ///< Enable or disable non-blocking mode on the socket
                fionread        = FIONREAD,     ///< Determine the amount of data which can be read atomically from socket
                siocatmark      = SIOCATMARK    ///< Determine whether or not all out-of-band data has been read
        };

        /// Flags to use in the convenience constructor
        enum mode {
                passive,        ///< Create a sock_stream socket and put it in
                                /// listen mode.
                active          ///< Create a sock_stream socket and connect it.
        };
        
        /// Levels of socket options for the getsockopt/setsockopt methods
        enum optlevel {
                sol_socket      = SOL_SOCKET,
                ipproto_tcp     = IPPROTO_TCP
        };
        
        /// Option names for the getsockopt/setsockopt methods
        enum optname {
                so_acceptconn   = SO_ACCEPTCONN,        ///< (bool) Socket is listen()ing.
                so_broadcast    = SO_BROADCAST,         ///< (bool) Socket is configured for the transmission of broadcast messages.
                so_debug        = SO_DEBUG,             ///< (bool) Debugging is enabled.
//                so_dontlinger   = SO_DONTLINGER,        ///< (bool) If true, the so_linger option is disabled..
                so_dontroute    = SO_DONTROUTE,         ///< (bool) Routing is disabled.
                so_error        = SO_ERROR,             ///< (int) Retrieve error status and clear.
                so_keepalive    = SO_KEEPALIVE,         ///< (bool Keepalives are being sent.
                so_linger       = SO_LINGER,            ///< (struct linger *) Returns the current linger options.
                so_oobinline    = SO_OOBINLINE,         ///< (bool) Out-of-band data is being received in the normal data stream.
                so_rcvbuf       = SO_RCVBUF,            ///< (int) Buffer size for receives
                so_reuseaddr    = SO_REUSEADDR,         ///< (bool) The socket may be bound to an address which is already in use.
                so_sndbuf       = SO_SNDBUF,            ///< (int) Buffer size for sends
                so_type         = SO_TYPE,              ///< (int) The type of the socket (e.g. sock_stream).
                tcp_nodelay     = TCP_NODELAY           ///< (bool) Disables the Nagle algorithm for send coalescing.
        };


        // Here are the two data members of socklib::base

        /**     \brief The descriptor of this socket

                This usually have the size of an int, but may be signed or
                unsigned depending on the platform. You should never use
                < or > to compare it with any other value. The only useful value
                to compare is socklib::base::invalid_socket, and you should always
                test using \a == or \a !=.
        */
        socket_type fd;
        
        
        /**     \brief The error code of the last operation performed.
        
                If no error ocurred in the last operation, the value is zero.

                \note   For some operations this is the only way to detect if
                        an error ocurred. e.g. accept()
        */
        mutable int error;
        

        /**     \brief Default constructor.

                It doesn't allocate resources for a socket, you have to call
                create() to actually allocate a socket.
                \see create()
        */
        base() throw() : fd(invalid_socket), error(0)  {}

        /**     \brief Constructor that uses an already created socket descriptor.
                \param _fd      a socket_type descriptor.
        */
        base(socket_type _fd) throw() : fd(_fd), error(0) {}

        /// Constructs and allocates a new socket.
        base(format, type, protocol = proto_ip) throw();


        /// Accept incomming connections in a listening socket
        base accept() throw();

        /** \brief      Accepts incomming connections in a listening socket
                        and stores the accepted address. */
        base accept(addr&) throw();


        /// Bind the socket to a specific address
        bool bind(const addr&) throw();


        /// Close the socket, releasing the resources used by it
        bool close() throw();


        /// Create a connection to the specified address
        bool connect(const addr&) throw();


        /// Get the address of the peer at which the socket is connected
        bool peername(addr&) const throw();
        

        /// Get the local address of the socket.
        bool sockname(addr&) throw();


        /* TODO (DanielKO#2#): Support the so_linger option */
        /// Retrieve a socket option
        bool getsockopt(optlevel, optname, int&) const throw();
        /// Retrieve a socket option
        bool getsockopt(optlevel, optname, bool&) const throw();


        /// Control the mode of a socket
        bool ioctl(ioctl_cmd, unsigned long&) throw();


        /// Put the socket in passive (listen) mode for incomming connections
        bool listen(int=3) throw();
        

        /// Receive data from a socket
        int recv(char *, int, msgflag=msg_none) throw();


        /// Receive a datagram and store the source address
        int recvfrom(addr&, char*, int, msgflag=msg_none) throw();


        // for select()
        /// Check if the socket has data to be read
        bool can_read(int=0, int=0) throw();
        /// Check if the socket can be written to
        bool can_write(int=0, int=0) throw();
        /// Checks for the presence of out-of-band data or if any exceptional error conditions.
        bool can_except(int=0, int=0) throw();


        /// Send data on a connected socket
        int send(const char*, int, msgflag=msg_none) throw();


        /// Send data to a specific destination
        int sendto(const addr&, const char*, int, msgflag=msg_none) throw();

        /// Set a socket option.
        /* TODO (DanielKO#2#): Support the so_linger option */
        bool setsockopt(optlevel, optname, int) throw();


        /// Disable sends and/or receives on a socket
        bool shutdown(shuthow) throw();
        
        
        /// Create a new socket
        bool create(format, type, protocol = proto_ip) throw();

        /// Synonym for create()
        inline bool socket(format fmt, type tp, protocol pr = proto_ip) throw()
        {       return create(fmt, tp, pr);     }




        // ****************** Convenience functions **********************


        /// Send all data.
        bool sendall(const char*, int, msgflag=msg_none) throw();


        /// Recv all data.
        bool recvall(char *, int, msgflag=msg_none) throw();


        /// Set the blocking mode
        bool block(bool) throw();


        /// Query if it can reuse the addr
        bool reuseaddr() const throw();

        /// Set the reuse addr option
        bool reuseaddr(bool) throw();


        /// Get the size of the internal send buffer
        int sendbuf() const throw();

        /// Set the size of the internal send buffer
        void sendbuf(int) throw();
        

        /// Get the size of the internal recv buffer
        int recvbuf() const throw();

        /// Set the size of the internal recv buffer
        void recvbuf(int) throw();

        /* TODO (DanielKO#2#): Add more convenience methods for {set|get}sockopt */

};



}

#endif
